Skip to content

feat(issue-1236): enhance docker build with additional build configuration#1284

Open
EdouardCourty wants to merge 8 commits into
aws:mainfrom
EdouardCourty:feat/enhance-docker-build-with-additional-arguments
Open

feat(issue-1236): enhance docker build with additional build configuration#1284
EdouardCourty wants to merge 8 commits into
aws:mainfrom
EdouardCourty:feat/enhance-docker-build-with-additional-arguments

Conversation

@EdouardCourty

Copy link
Copy Markdown

Description

Add support for two new options in the runtime configurations for Docker-based builds :

  • change build context path with buildContextPath
  • custom docker build arguments with customDockerBuildArgs

Both are optional and this change is backward-compatible.

Related Issue

Closes #1236

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (please describe):

Testing

How have you tested the change?

  • I ran npm run test:unit and npm run test:integ
  • I ran npm run typecheck
  • I ran npm run lint
  • If I modified src/assets/, I ran npm run test:update-snapshots and committed the updated snapshots

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the
terms of your choice.

…ation

- allow buildContextPath argument to change build context path
- allow custom docker build arguments
@github-actions github-actions Bot added the size/m PR size: M label May 18, 2026
@EdouardCourty EdouardCourty marked this pull request as ready for review May 18, 2026 15:42
@EdouardCourty EdouardCourty requested a review from a team May 18, 2026 15:42
@github-actions github-actions Bot added the agentcore-harness-reviewing AgentCore Harness review in progress label May 21, 2026

@agentcore-cli-automation agentcore-cli-automation left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! The CLI-side wiring (schema, dev server, local packager) is clean and well-tested.

However, I think there is one blocking correctness issue: the new buildContextPath and customDockerBuildArgs fields are not honored by agentcore deploy — only by agentcore dev and the local agentcore package validation. The deploy path goes through the CDK construct (@aws/agentcore-cdkAgentCoreApplicationContainerSourceAsset + ContainerBuildProject), which:

  1. Uploads only codeLocation as the source asset (so buildContextPath outside codeLocation is impossible — files referenced in the Dockerfile won't be in the upload).
  2. Runs a hard-coded buildspec: docker build -t $IMAGE_URI -f $DOCKERFILE_PATH . — no --build-arg flags, no overridable context.
  3. Does not include buildContextPath / customDockerBuildArgs in its own AgentEnvSpec schema, so even if the CLI sends them, zod will strip them on the construct side.

The net result is that a user following the new "Shared Dockerfile (monorepo)" docs example will see it work locally with agentcore dev and agentcore package, but agentcore deploy will silently use the wrong context (just codeLocation) and no build args, producing either a broken image or a deploy-time docker build failure with a confusing error.

See inline comments for specifics and options to address this. Other comments are smaller follow-ups.

const buildResult = spawnSync(
runtime,
['build', '-t', imageName, '-f', dockerfilePath, ...getUvBuildArgs(), codeLocation],
['build', '-t', imageName, '-f', dockerfilePath, ...getUvBuildArgs(), ...buildArgFlags, buildContext],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking — feature not honored on agentcore deploy.

ContainerPackager is only used by agentcore package (local validation) and agentcore dev. The actual deploy path goes through the CDK construct (@aws/agentcore-cdk), where:

  • ContainerSourceAsset (in agentcore-l3-cdk-constructs) uploads only resolveCodeLocation(spec.codeLocation, configRoot) as the source asset — buildContextPath is never read, so files referenced from outside codeLocation won't be uploaded.
  • ContainerBuildProject's buildspec hard-codes docker build -t $IMAGE_URI -f $DOCKERFILE_PATH . with no --build-arg plumbing.
  • The L3 construct's own AgentEnvSpec schema doesn't include these new fields, so zod will strip them on the construct side anyway.

This means the documented "Shared Dockerfile (monorepo)" workflow in docs/container-builds.md will work locally and silently break (or behave wrongly) on deploy. That is a serious foot-gun.

Options to address:

  1. (Preferred) Land a paired change in aws/agentcore-l3-cdk-constructs first that:
    • Adds buildContextPath and customDockerBuildArgs to AgentEnvSpecSchema.
    • Updates ContainerSourceAsset to upload buildContextPath (when set) instead of codeLocation, and adjusts the dockerfile path so it remains valid relative to that asset.
    • Updates ContainerBuildProject's buildspec / ContainerImageBuilder to forward --build-arg flags through CodeBuild env vars (e.g. via the custom resource properties, with the buildspec interpolating $BUILD_ARGS).
    • Bumps the construct dep here once published.
  2. Hold this PR until that construct change is merged + published, and gate the schema additions in this repo on having the new construct version.
  3. As an interim, validate in the CLI's schema (or in the deploy command) that these fields are not set and reject with a clear "only supported for local dev/package" error — but that significantly waters down the feature and the docs example would have to be removed.

I'd lean towards option 1. Happy to help review the construct-side PR.

Comment thread docs/container-builds.md
}
```

The shared `Dockerfile` can then branch on the build arg:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tied to the deploy issue: this example will not work end-to-end with agentcore deploy until the CDK construct side is updated. If we go with the "land construct first" path that's fine; otherwise this doc section should be reworded to make clear this is local-dev-only, or removed.

Comment thread docs/container-builds.md
"customDockerBuildArgs": { "AGENT_NAME": "agent-one" }
},
{
"name": "agent-two",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate issue with this example regardless of the deploy plumbing: dockerfile is validated as a filename (no path separators — see src/schema/schemas/agent-env.ts and getDockerfilePath in src/lib/constants.ts), and getDockerfilePath joins it onto codeLocation. So the implied "single Dockerfile at the project root" setup isn't achievable today — each agent's codeLocation would still need its own Dockerfile file (even if it's just a one-liner that delegates).

Either (a) extend dockerfile to accept a path relative to the build context (and resolve it accordingly), or (b) update this section to clarify that each codeLocation must still contain a Dockerfile (which can simply be a thin wrapper / symlink) and adjust the example accordingly.

Comment thread src/schema/schemas/agent-env.ts Outdated
* Useful for parameterising a shared Dockerfile per agent (e.g. `{ "AGENT_NAME": "myagent" }`).
* Container builds only.
*/
customDockerBuildArgs: z.record(z.string().min(1), z.string()).optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, but worth considering: the existing AgentEnvSpecSchema in the sibling construct repo already has a comment near this validator block (If adding more Container-specific fields, consider consolidating into a containerConfig object (see networkConfig pattern)). With this PR we're now up to three flat container-only fields (dockerfile, buildContextPath, customDockerBuildArgs) plus the cross-cutting validators. Grouping these under a single optional containerConfig: { dockerfile?, buildContextPath?, customDockerBuildArgs? } would make the cross-build-type validation simpler (build !== 'Container' && data.containerConfig becomes one check) and is more future-proof. Not strictly required for this PR, but if you agree it's worth doing it now before this ships.

@github-actions github-actions Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label May 21, 2026
@aidandaly24

Copy link
Copy Markdown
Contributor

Hey @EdouardCourty after reviewing it seems the above comments are true regarding this feature not being honored by agentcore deploy. If you make these suggested updates we'd love to get this merged in.

I can also go ahead and make the necessary updates if there is no activity on this PR by 06/05/2026 or with your explicit permission. Thank you!

@EdouardCourty

Copy link
Copy Markdown
Author

Hey @aidandaly24 if you can make the needed changes to aws/agentcore-l3-cdk-constructs, go ahead!

I could not find the repository myself as it does not seem to be public (the NPM package page has a GitHub link which shows a 404...).

@nxf5025

nxf5025 commented Jul 7, 2026

Copy link
Copy Markdown

@aidandaly24 - I've been successfully running this branch as a fork of the CLI specifically for the agentcore dev functionality. Since the other projects are not public does it make sense to get this out for now and the agentcore deploy can come later? Just brainstorming here as it seems we are stuck.

@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Jul 7, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Jul 7, 2026
@tejaskash

Copy link
Copy Markdown
Contributor

@nxf5025 We discussed this, and @aidandaly24 will make the changes to L3 CDK.

Resolves the agent-env schema conflict (keeps both the new Container-only
validators and main's filesystemConfigurations block) and completes the
feature so it is honored on `agentcore deploy`, not just dev/package:

- Resolve `dockerfile` relative to the build context (`buildContextPath ??
  codeLocation`) in the packager, dev server, and deploy preflight; relax
  `getDockerfilePath` and the schema to allow a safe relative subpath (no
  absolute paths / no `..` traversal). This makes the shared-Dockerfile
  monorepo example in docs actually work end to end.
- Tighten `customDockerBuildArgs` keys to valid identifiers (they become
  CodeBuild env vars / Dockerfile ARG names on deploy).
- Docs: clarify Dockerfile-relative-to-context resolution and deploy parity.

The paired @aws/agentcore-cdk change (honoring both fields in CodeBuild) lands
in aws/agentcore-l3-cdk-constructs.
…ld-context resolution

Address adversarial-review findings (paired with the @aws/agentcore-cdk change):

- Reject customDockerBuildArgs keys reserved by the CodeBuild build environment
  (ECR_REGISTRY, IMAGE_URI, DOCKERFILE_PATH, BUILD_ARG_FLAGS, or CODEBUILD_*/AWS_*
  prefixes) so a config that builds locally can't fail only on `agentcore deploy`.

Tests:
- schema: reserved-key rejection.
- packager: assert the Dockerfile (-f) resolves against the build context, not codeLocation.
- dev server: assert customDockerBuildArgs are forwarded and buildContextPath is used as
  both the build context and the Dockerfile root.
@github-actions github-actions Bot added size/l PR size: L and removed size/m PR size: M labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Package Tarball

aws-agentcore-0.22.0.tgz

How to install

gh release download pr-1284-tarball --repo aws/agentcore-cli --pattern "*.tgz" --dir /tmp/pr-tarball
npm install -g /tmp/pr-tarball/aws-agentcore-0.22.0.tgz

@aidandaly24

Copy link
Copy Markdown
Contributor

Hey @nxf5025 @EdouardCourty, going to get this reviewed and it will be released this Thursday. Sorry for the delay!

…t .dockerignore

Address code-review findings (paired with @aws/agentcore-cdk):

- Reserved build-arg keys: narrow the denylist to genuinely build-breaking names
  (PATH, HOME, IFS, LD_PRELOAD, LD_LIBRARY_PATH) plus the docker-client vars
  (DOCKER_HOST, DOCKER_CONFIG, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH); stop
  over-rejecting common ARGs (USER, LANG, ...).
- Constrain customDockerBuildArgs values (max 4096, no control chars) so a
  schema-valid config can't be rejected by CodeBuild only on deploy.
- Deduplicate the --build-arg flag construction into getCustomBuildArgs() in
  build-args.ts (used by the package and dev build paths).
- deploy preflight: warn when buildContextPath is set but the context root has no
  .dockerignore (secrets/junk would otherwise be sent to Docker/CodeBuild). Both
  local and deploy honor the same root .dockerignore.
- docs + compacted schema (@regex/@max) updated.

npm test (5836), typecheck, lint, build all green.
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Jul 8, 2026
…textPath is set

Closes the secret-exposure gap without reintroducing the local/deploy divergence:
when a Container agent sets buildContextPath and the build-context root has no
.dockerignore, the CLI generates one (excluding .env, .env.*, .git/, node_modules/,
.venv/, __pycache__/, agentcore/). Because it's a real file, both local
`docker build` and the CodeBuild deploy path honor the same rules — secrets/junk stay
out of the image and the S3 upload, consistently, with no forced-exclude false
positives (users edit the file to include anything intentional). This mirrors the
CodeZip packager's always-on secret guard.

- New shared helper ensureBuildContextDockerignore(); never overwrites an existing file.
- Wired into the package, dev, and deploy-preflight build paths (replaces the prior
  "no .dockerignore" warning with actually creating one + a log line).
- docs updated; unit tests for the helper (create/no-op) and the preflight wiring.

Validated live end to end: the generated .dockerignore trimmed the CodeBuild context
(agentcore/, .git, .venv excluded) and the build succeeded.
@github-actions github-actions Bot removed the size/l PR size: L label Jul 8, 2026
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Jul 8, 2026
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Jul 8, 2026
`agentcore package` built its local image tag as `agentcore-package-${agentName}`
without lowercasing, so `docker build -t` rejected any capitalized agent name
(e.g. the default `AgentOne`) with "repository name must be lowercase" — the
package command was broken for essentially every default container agent. The dev
server already lower-cases its tag; the packager now matches.

Pre-existing bug (unrelated to buildContextPath/customDockerBuildArgs), surfaced
while live-testing `agentcore package` on a container agent. Added a unit test with
a mixed-case name.
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Jul 9, 2026

@tejaskash tejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review of the container-build enhancements. 8 findings below — 4 are correctness blockers (all but one share a single root cause), the rest are cleanup.

// When the context is widened via buildContextPath, ensure a .dockerignore keeps secrets/junk out
// of the local image — the same file the deploy (CodeBuild) path honors, so both stay consistent.
if (this.config.buildContextPath) {
const created = ensureBuildContextDockerignore(buildContext);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker — correctness] dev crashes instead of reporting a config error.

ensureBuildContextDockerignore(buildContext) runs here inside prepare(), before the graceful missing-Dockerfile guard below, and calls writeFileSync with no try/catch. A buildContextPath that resolves to a missing or read-only directory throws ENOENT/EACCES, so prepare() never returns false — the throw propagates through await this.prepare(). In the TUI, useDevServer fires void startServer() with no catch around await server.start(), so this becomes an unhandled rejection that crashes the dev process; the web-ui path degrades to a generic 500 instead of the {success:false} handling the if (!child) block provides.

Shares a root cause with the preflight and packager comments: make ensureBuildContextDockerignore guard the directory's existence and run it after validation, via a shared notification callback.

// into the image or uploaded to CodeBuild. Both local and deploy honor this same file; it is
// created only when absent (never overwrites the user's).
if (agent.buildContextPath) {
const created = ensureBuildContextDockerignore(buildContext);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker — correctness] Preflight masks its own friendly error and writes into the repo on a failing deploy.

ensureBuildContextDockerignore guards only the .dockerignore file's existence, never the directory. With buildContextPath: './typo-dir' (missing), the loop first pushes a clean "Dockerfile not found" into errors[], but this call then writeFileSyncs into the missing dir and throws raw ENOENT before the aggregated throw — so the user sees a cryptic fs error instead of the actionable one. When the dir exists but the Dockerfile is missing, it still leaves a stray .dockerignore in the repo even though the deploy aborts (dirtying CI/git checkouts).

Note the inconsistency with ContainerPackager.pack(), which does the Dockerfile existsSync check after this write; preflight does it before. Fix both by guarding the dir and deferring the write until validation passes.

// When the context is widened via buildContextPath, ensure a .dockerignore keeps secrets/junk out
// of the local image — the same file the deploy (CodeBuild) path honors, so both stay consistent.
if (spec.buildContextPath) {
ensureBuildContextDockerignore(buildContext);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker — correctness] package throws an untyped fs error and leaves a stray .dockerignore.

pack() calls ensureBuildContextDockerignore here, before the existsSync(dockerfilePath) check that used to be its first guard. A bad buildContextPath throws a bare Error (ENOENT), bypassing the PackagingError path — telemetry's classifyError only maps BaseError, so it lands in UnknownError and the user sees a cryptic message. With a valid dir but a missing Dockerfile, the .dockerignore is written into the repo before pack() rejects.

Same root cause as the dev-server and preflight comments. Also: preflight validates the Dockerfile before this write, so the two callers are inconsistent — centralize the ordering.

# CodeBuild. Both 'agentcore dev'/'package' and 'agentcore deploy' honor this file. Edit freely; to
# intentionally include a file listed here (e.g. a non-secret .env.production), remove its line.
.env
.env.*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker — correctness] Generated .dockerignore doesn't exclude nested node_modules/secrets in the monorepo case.

These patterns (node_modules/, .venv/, __pycache__/, .env, ...) have no **/ prefix. Docker matches .dockerignore root-relative via Go filepath.Match, so node_modules/ matches only the top-level directory. In the feature's headline case (buildContextPath: '.'), nested app/agent-one/node_modules and per-package .env files are still uploaded to the Docker context and CodeBuild — directly contradicting the module/inline comments that this keeps secrets and build junk out of the upload. A leaked nested .env is security-adjacent, not just a size regression.

Use **/node_modules, **/.env, **/__pycache__, etc.

Comment thread src/lib/constants.ts
const name = dockerfile ?? DOCKERFILE_NAME;
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
throw new Error(`Invalid dockerfile name: must be a filename without path separators or traversal`);
if (name.startsWith('/') || name.includes('\\') || name.split('/').includes('..')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Cleanup — reuse/altitude] Dockerfile-path validation is duplicated and already divergent.

The same "relative subpath, no leading slash, no backslash, no .." rule is now maintained in two places this PR edits in lockstep: this runtime guard in getDockerfilePath and the Zod DockerfilePathSchema regex+refine in agent-env.ts. They already disagree — the schema's ^[a-zA-Z0-9] anchor rejects .docker/Dockerfile, but this guard accepts it (no leading /, no \, no .. segment). Extract a single isValidDockerfilePath predicate used by both. Non-blocking, but worth doing while this code is open since the divergence is live.

const configBaseDir = options.artifactDir ?? options.projectRoot ?? process.cwd();
const codeLocation = resolveCodeLocation(spec.codeLocation, configBaseDir);
const dockerfilePath = getDockerfilePath(codeLocation, spec.dockerfile);
const buildContext = spec.buildContextPath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Cleanup — altitude] Build-context resolution re-derived at 4 sites with two different resolvers.

buildContextPath ?? codeLocation (then resolve) is re-implemented here, in preflight.ts (~line 210), and in container-dev-server.ts (~line 70) — while dev/config.ts resolves via a different private function (resolveCodeDirectory, which strips trailing slashes and uses join) than deploy/package (resolveCodeLocation, which uses resolve). The PR's own comments insist all paths must resolve context identically, so this is a latent local-vs-deploy divergence: a future path-normalization tweak applied to one resolver silently makes agentcore dev build from a different context than deploy. Collapse into one exported resolveBuildContext(spec, baseDir). Non-blocking.

const BuildArgKeySchema = z
.string()
.regex(
/^[A-Za-z_][A-Za-z0-9_]*$/,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Cleanup — reuse] BuildArgKeySchema re-declares EnvVarNameSchema's regex.

This regex /^[A-Za-z_][A-Za-z0-9_]*$/ is byte-identical to the exported EnvVarNameSchema above. Build args become CodeBuild env-var names, so reuse EnvVarNameSchema (with a custom message) — nothing is lost, since the RESERVED_BUILD_ARG_KEYS check lives separately in the superRefine. Non-blocking.

.min(1)
.max(255)
.regex(
/^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Cleanup — low severity] Broadened dockerfile regex accepts a directory-shaped value.

Widening the regex to allow / for subpaths also lets a trailing-slash directory value like docker/ through (docker/Dockerfile/ and a//b are caught cleanly by existsSync/join normalization, but a value naming a real directory is not). That defers failure to docker build -f <dir> with a confusing error instead of rejecting at config-validation time. Low severity; a .refine rejecting a trailing slash closes it.

@tejaskash tejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on the 4 correctness blockers flagged in the inline comments above (dev-server crash, preflight error-masking + repo write, packager untyped error + stray file, and the nested .dockerignore secret/junk leak). Findings 1–3 share one root cause: guard the directory in ensureBuildContextDockerignore and run it after validation via a shared notification callback; finding 4 is a **/-prefix fix on the template. The 4 cleanup items are non-blocking. (Refuted candidates dropped; add-agent wizard subpath conflict is pre-existing → follow-up ticket.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(config): support shared Dockerfile via buildContextPath and customDockerBuildArgs in agentcore.json

5 participants